package edu.northwestern.cbits.purple_robot_manager.probes.builtin;
import org.apache.commons.math3.complex.Complex;
import org.apache.commons.math3.transform.DftNormalization;
import org.apache.commons.math3.transform.FastFourierTransformer;
import org.apache.commons.math3.transform.TransformType;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.support.v4.content.ContextCompat;
import java.util.Map;
import edu.northwestern.cbits.purple_robot_manager.Manifest;
import edu.northwestern.cbits.purple_robot_manager.R;
import edu.northwestern.cbits.purple_robot_manager.activities.settings.FlexibleListPreference;
import edu.northwestern.cbits.purple_robot_manager.logging.LogManager;
import edu.northwestern.cbits.purple_robot_manager.logging.SanityCheck;
import edu.northwestern.cbits.purple_robot_manager.logging.SanityManager;
import edu.northwestern.cbits.purple_robot_manager.probes.Probe;
public class AudioFeaturesProbe extends Probe
{
private static final boolean DEFAULT_ENABLED = false;
private static final String ENABLED = "config_probe_audio_feature_enabled";
private static final String FREQUENCY = "config_probe_audio_features_frequency";
private static final String DURATION = "config_probe_audio_features_duration";
private static final String DEFAULT_DURATION = "15000";
private static final String MAX_SAMPLE_RATE = "config_probe_audio_features_max_sample_rate";
private static final String DEFAULT_MAX_SAMPLE_RATE = "11025";
private boolean _recording = false;
private long _lastCheck = 0;
@Override
public String getPreferenceKey() {
return "built_in_audio_features";
}
@Override
public String name(Context context)
{
return "edu.northwestern.cbits.purple_robot_manager.probes.builtin.AudioFeaturesProbe";
}
@Override
public String title(Context context)
{
return context.getString(R.string.title_audio_features_probe);
}
@Override
public String probeCategory(Context context)
{
return context.getString(R.string.probe_sensor_category);
}
@Override
@SuppressWarnings("deprecation")
public PreferenceScreen preferenceScreen(Context context, PreferenceManager manager)
{
PreferenceScreen screen = super.preferenceScreen(context, manager);
screen.setTitle(this.title(context));
screen.setSummary(this.summary(context));
CheckBoxPreference enabled = new CheckBoxPreference(context);
enabled.setTitle(R.string.title_enable_probe);
enabled.setKey(AudioFeaturesProbe.ENABLED);
enabled.setDefaultValue(AudioFeaturesProbe.DEFAULT_ENABLED);
screen.addPreference(enabled);
FlexibleListPreference frequency = new FlexibleListPreference(context);
frequency.setKey(AudioFeaturesProbe.FREQUENCY);
frequency.setEntryValues(R.array.probe_satellite_frequency_values);
frequency.setEntries(R.array.probe_satellite_frequency_labels);
frequency.setTitle(R.string.probe_frequency_label);
frequency.setDefaultValue(Probe.DEFAULT_FREQUENCY);
screen.addPreference(frequency);
FlexibleListPreference duration = new FlexibleListPreference(context);
duration.setKey(AudioFeaturesProbe.DURATION);
duration.setEntryValues(R.array.probe_audio_feature_duration_values);
duration.setEntries(R.array.probe_audio_feature_duration_labels);
duration.setTitle(R.string.probe_audio_feature_duration_label);
duration.setDefaultValue(AudioFeaturesProbe.DEFAULT_DURATION);
screen.addPreference(duration);
FlexibleListPreference sampleRate = new FlexibleListPreference(context);
sampleRate.setKey(AudioFeaturesProbe.MAX_SAMPLE_RATE);
sampleRate.setEntryValues(R.array.probe_audio_feature_sample_rate_values);
sampleRate.setEntries(R.array.probe_audio_feature_sample_rate_labels);
sampleRate.setTitle(R.string.probe_audio_feature_sample_rate_label);
sampleRate.setDefaultValue(AudioFeaturesProbe.DEFAULT_MAX_SAMPLE_RATE);
screen.addPreference(sampleRate);
return screen;
}
@Override
public boolean isEnabled(final Context context)
{
if (super.isEnabled(context))
{
SharedPreferences prefs = Probe.getPreferences(context);
boolean enabled = prefs.getBoolean(AudioFeaturesProbe.ENABLED, AudioFeaturesProbe.DEFAULT_ENABLED);
if (this._recording == false && enabled)
{
if (ContextCompat.checkSelfPermission(context, "android.permission.RECORD_AUDIO") == PackageManager.PERMISSION_GRANTED) {
long now = System.currentTimeMillis();
long freq = Long.parseLong(prefs.getString(AudioFeaturesProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY));
final int duration = Integer.parseInt(prefs.getString(AudioFeaturesProbe.DURATION, AudioFeaturesProbe.DEFAULT_DURATION));
final int sampleRate = Integer.parseInt(prefs.getString(AudioFeaturesProbe.MAX_SAMPLE_RATE, AudioFeaturesProbe.DEFAULT_MAX_SAMPLE_RATE));
if (now - this._lastCheck > freq) {
this._recording = true;
final AudioFeaturesProbe me = this;
Runnable r = new Runnable() {
@Override
@SuppressWarnings("deprecation")
public void run() {
int bufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
AudioRecord recorder = null;
int[] rates = new int[]{44100, 22050, 11025, 8000};
for (int rate : rates) {
if (rate <= sampleRate && recorder == null) {
AudioRecord newRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, rate, AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize);
if (newRecorder.getState() == AudioRecord.STATE_INITIALIZED)
recorder = newRecorder;
else
newRecorder.release();
}
}
if (recorder == null)
{
me._recording = false;
SanityManager.getInstance(context).addAlert(SanityCheck.WARNING, context.getString(R.string.name_sanity_audio_features_title), context.getString(R.string.name_sanity_audio_features_warning), null);
return;
}
int sampleRate = recorder.getSampleRate();
SanityManager.getInstance(context).clearAlert(context.getString(R.string.name_sanity_audio_features_title));
int sampleCount = sampleRate * (duration / 1000);
int twoPower = 2;
while (sampleCount > twoPower)
twoPower *= 2;
sampleCount = twoPower / 2;
if (recorder != null) {
double[] samples = new double[sampleCount];
recorder.startRecording();
short[] buffer = new short[bufferSize];
int index = 0;
int read = 0;
double sampleSum = 0;
double samplePower = 0;
while (index < samples.length && 0 <= (read = recorder.read(buffer, 0, bufferSize))) {
for (int i = 0; i < read; i++) {
if (index < samples.length) {
sampleSum += Math.abs(buffer[i]);
samplePower += Math.pow(((double) buffer[i]) / Short.MAX_VALUE, 2);
samples[index] = (double) buffer[i];
index += 1;
}
}
}
recorder.stop();
Bundle bundle = new Bundle();
bundle.putString("PROBE", me.name(context));
bundle.putLong("TIMESTAMP", System.currentTimeMillis() / 1000);
bundle.putInt("SAMPLE_RATE", sampleRate);
bundle.putInt("SAMPLE_BUFFER_SIZE", samples.length);
bundle.putInt("SAMPLES_RECORDED", index);
recorder.release();
FastFourierTransformer fft = new FastFourierTransformer(DftNormalization.STANDARD);
Complex[] values = fft.transform(samples, TransformType.FORWARD);
double maxFrequency = 0;
double maxMagnitude = Double.NEGATIVE_INFINITY;
double minMagnitude = Double.POSITIVE_INFINITY;
for (int i = 0; i < values.length / 2; i++) {
Complex value = values[i];
double magnitude = value.abs();
if (magnitude > maxMagnitude) {
maxMagnitude = magnitude;
maxFrequency = (i * sampleRate) / (double) samples.length;
}
if (magnitude < minMagnitude)
minMagnitude = magnitude;
}
bundle.putDouble("FREQUENCY", maxFrequency);
bundle.putDouble("NORMALIZED_AVG_MAGNITUDE", (sampleSum / Short.MAX_VALUE) / samples.length);
bundle.putDouble("POWER", samplePower / samples.length);
me.transmitData(context, bundle);
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
me._recording = false;
}
};
Thread t = new Thread(r);
t.start();
this._lastCheck = now;
}
}
else
SanityManager.getInstance(context).addPermissionAlert(this.name(context), "android.permission.RECORD_AUDIO", context.getString(R.string.rationale_audio_features_probe), null);
}
return enabled;
}
return false;
}
@Override
public String summary(Context context)
{
return context.getString(R.string.summary_audio_features_probe_desc);
}
@Override
public void enable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(AudioFeaturesProbe.ENABLED, true);
e.commit();
}
@Override
public void disable(Context context)
{
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putBoolean(AudioFeaturesProbe.ENABLED, false);
e.commit();
}
@Override
public String summarizeValue(Context context, Bundle bundle)
{
double freq = bundle.getDouble("FREQUENCY");
return String.format(context.getResources().getString(R.string.summary_audio_features_probe), freq);
}
@Override
public JSONObject fetchSettings(Context context)
{
JSONObject settings = super.fetchSettings(context);
try
{
JSONArray values = new JSONArray();
values.put(true);
values.put(false);
JSONObject frequency = new JSONObject();
frequency.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG);
values = new JSONArray();
String[] options = context.getResources().getStringArray(R.array.probe_satellite_frequency_values);
for (String option : options)
{
values.put(Long.parseLong(option));
}
frequency.put(Probe.PROBE_VALUES, values);
settings.put(Probe.PROBE_FREQUENCY, frequency);
JSONObject duration = new JSONObject();
duration.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG);
values = new JSONArray();
String[] durationOptions = context.getResources().getStringArray(R.array.probe_audio_feature_duration_values);
for (String option : durationOptions)
{
values.put(Long.parseLong(option));
}
duration.put(Probe.PROBE_VALUES, values);
settings.put(Probe.PROBE_DURATION, duration);
JSONObject sample = new JSONObject();
sample.put(Probe.PROBE_TYPE, Probe.PROBE_TYPE_LONG);
values = new JSONArray();
String[] sampleOptions = context.getResources().getStringArray(R.array.probe_audio_feature_sample_rate_values);
for (String option : sampleOptions)
{
values.put(Long.parseLong(option));
}
sample.put(Probe.PROBE_VALUES, values);
settings.put(AudioFeaturesProbe.MAX_SAMPLE_RATE, sample);
}
catch (JSONException e)
{
LogManager.getInstance(context).logException(e);
}
return settings;
}
@Override
public Map<String, Object> configuration(Context context)
{
Map<String, Object> map = super.configuration(context);
SharedPreferences prefs = Probe.getPreferences(context);
try
{
long freq = Long.parseLong(prefs.getString(AudioFeaturesProbe.FREQUENCY, Probe.DEFAULT_FREQUENCY));
map.put(Probe.PROBE_FREQUENCY, freq);
long duration = Long.parseLong(prefs.getString(AudioFeaturesProbe.DURATION, AudioFeaturesProbe.DEFAULT_DURATION));
map.put(Probe.PROBE_DURATION, duration);
long sampleRate = Long.parseLong(prefs.getString(AudioFeaturesProbe.MAX_SAMPLE_RATE, AudioFeaturesProbe.DEFAULT_MAX_SAMPLE_RATE));
map.put(AudioFeaturesProbe.MAX_SAMPLE_RATE, sampleRate);
}
catch (NumberFormatException e)
{
LogManager.getInstance(context).logException(e);
}
return map;
}
@Override
public void updateFromMap(Context context, Map<String, Object> params)
{
super.updateFromMap(context, params);
if (params.containsKey(Probe.PROBE_FREQUENCY))
{
Object frequency = params.get(Probe.PROBE_FREQUENCY);
if ((frequency instanceof Double) == false)
frequency = Double.valueOf(frequency.toString()).longValue();
else
frequency = ((Double) frequency).longValue();
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putString(AudioFeaturesProbe.FREQUENCY, frequency.toString());
e.commit();
}
if (params.containsKey(Probe.PROBE_DURATION))
{
Object duration = params.get(Probe.PROBE_DURATION);
if ((duration instanceof Double) == false)
duration = Double.valueOf(duration.toString()).longValue();
else
duration = ((Double) duration).longValue();
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putString(AudioFeaturesProbe.DURATION, duration.toString());
e.commit();
}
if (params.containsKey(AudioFeaturesProbe.MAX_SAMPLE_RATE))
{
Object rate = params.get(AudioFeaturesProbe.MAX_SAMPLE_RATE);
if ((rate instanceof Double) == false)
rate = Double.valueOf(rate.toString()).longValue();
else
rate = ((Double) rate).longValue();
SharedPreferences prefs = Probe.getPreferences(context);
Editor e = prefs.edit();
e.putString(AudioFeaturesProbe.MAX_SAMPLE_RATE, rate.toString());
e.commit();
}
}
}